﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace JetBlack.TopicBus.ExcelAddin
{
    [ProgId(Subscriber.ProgId)]
    [ComVisible(true)]
    [Guid("D23CCCD4-1F3B-4654-8F65-09FCFE075A90")]
    public class Subscriber : IRtdServer, IDisposable
    {
        public const string ProgId = "JetBlack.TopicSubscriber";

        private IRTDUpdateEvent updateCallback;
        private readonly IDictionary<ClientKey, CachedClient> clientCache = new Dictionary<ClientKey, CachedClient>();
        private readonly ClientKey defaultClientKey;

        public Subscriber()
        {
            string hostName = AssemblyEnvironment.AppSettings["host"];
            int port;
            if (!String.IsNullOrEmpty(hostName) && Int32.TryParse(AssemblyEnvironment.AppSettings["port"], out port))
                defaultClientKey = new ClientKey(hostName, port);
            else
                defaultClientKey = null;
        }

        #region IRtdServer Members

        public object ConnectData(int TopicID, ref Array Strings, ref bool GetNewValues)
        {
            // This method is called when an =RTD(<ComObject>, [<Server>], arg1 [, arg2 [, ... argn ]]] ) function is called. The function
            // passes a handle "TopicID" and the string arguments.
            //
            // The format of the request should be <Topic>, <FieldName> [,<DefaultValue> [,<hostName>,<port>]]

            string topic = Strings.GetValue(0) as string;
            string fieldName = Strings.GetValue(1) as string;
            object defaultValue = ExcelUtils.XlErrNA;
            ClientKey clientKey = defaultClientKey;

            if (Strings.Length >= 3)
            {
                defaultValue = Strings.GetValue(2) as string;
                if (Strings.Length >= 4)
                {
                    if (Strings.Length != 5)
                        return ExcelUtils.XlErrValue;

                    string hostName = Strings.GetValue(3) as string;
                    int port;
                    if (!Int32.TryParse(Strings.GetValue(4) as string, out port))
                        return ExcelUtils.XlErrNA;

                    clientKey = new ClientKey(hostName, port);
                }
            }

            if (clientKey == null)
                return ExcelUtils.XlErrNA;

            lock (clientCache)
            {
                CachedClient cachedClient;
                if (!clientCache.TryGetValue(clientKey, out cachedClient))
                {
                    cachedClient = new CachedClient();
                    if (!cachedClient.Connect(clientKey.Host, clientKey.Port))
                        return ExcelUtils.XlErrNA;

                    cachedClient.OnUpdated += OnUpdated;
                    clientCache.Add(clientKey, cachedClient);
                }

                GetNewValues = true;

                return cachedClient.RegisterTopicId(TopicID, topic, fieldName);
            }
        }

        void OnUpdated()
        {
            if (updateCallback != null)
                updateCallback.UpdateNotify();
        }

        public void DisconnectData(int TopicId)
        {
            lock (clientCache)
            {
                foreach (CachedClient cachedClient in clientCache.Values)
                    if (cachedClient.HasTopic(TopicId))
                        cachedClient.UnregisterTopicId(TopicId);
            }
        }

        public int Heartbeat()
        {
            return 1;
        }

        public Array RefreshData(ref int TopicCount)
        {
            object[,] data = null;
            TopicCount = 0;

            lock (clientCache)
            {
                List<KeyValuePair<int, object>> values = new List<KeyValuePair<int, object>>();

                foreach (CachedClient cachedClient in clientCache.Values)
                    values.AddRange(cachedClient.GetUpdatedTopics());

                TopicCount = values.Count;
                data = new object[2, values.Count];
                int i = 0;
                foreach (KeyValuePair<int, object> item in values)
                {
                    data[0, i] = item.Key;
                    data[1, i] = item.Value;
                    ++i;
                }
            }

            return data;
        }

        public int ServerStart(IRTDUpdateEvent CallbackObject)
        {
            updateCallback = CallbackObject;
            return 1;
        }

        public void ServerTerminate()
        {
            this.updateCallback = null;

            foreach (CachedClient cachedClient in clientCache.Values)
                cachedClient.Dispose();
            clientCache.Clear();
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
